package schedule

import (
	"fmt"
	"log"
	"sync"
	"time"
	"xin-job/admin/dao"
	"xin-job/admin/dao/model"
	"xin-job/admin/trigger"
)

var PRE_READ_MS int64 = 5000

const (
	DO_NOTHING    = "DO_NOTHING"    // 默认什么也不做
	FIRE_ONCE_NOW = "FIRE_ONCE_NOW" // 重试一次
)

var instance = &ScheduleHelper{
	ringData: sync.Map{},
	stopFlag: make(chan struct{}),
}

func GetInstance() *ScheduleHelper {
	return instance
}

type ScheduleHelper struct {
	ringData sync.Map // 时间轮缓存

	stopFlag chan struct{}
}

func (s *ScheduleHelper) Start() {
	s.startSechedule()
	s.startRing()
}

func (s *ScheduleHelper) Stop() {
	s.stopFlag <- struct{}{}
}

func (s *ScheduleHelper) startSechedule() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Printf("scheduler err %v\n", err)
			}
		}()

		time.Sleep(time.Duration(5000-time.Now().UnixMilli()%1000) * time.Millisecond)

		for {
			select {
			case <-s.stopFlag:
				// TODO 优雅停机
			default:

				startTime := time.Now().UnixMilli()

				var preReadSuc = true

				// TODO 数据库分布式锁

				// 查询需要执行的任务
				// TODO 6000 xxl-job 中预估的快慢线程池每秒最大调度的任务数量

				nowTime := time.Now().UnixMilli()
				jobs := dao.ScheduleJobQuery(nowTime+PRE_READ_MS, 6000)
				if len(jobs) > 0 {
					for _, job := range jobs {
						if nowTime > job.TriggerNextTime+PRE_READ_MS {
							// 执行时间超过一次调度周期

							// 根据策略处置
							if job.MisfireStrategy == FIRE_ONCE_NOW {
								// TODO 枚举
								trigger.AddTrigger(job.Id, "MISFIRE", -1, "", "", "")
							}

							// 刷新下次执行时间
							s.refreshNextValidTime(job, time.Now())
						} else if nowTime > job.TriggerLastTime {
							// 任务过期，但是未超过一次调度周期
							// 触发一次
							// TODO 枚举
							trigger.AddTrigger(job.Id, "CRON", -1, "", "", "")

							// 刷新下次执行时间
							s.refreshNextValidTime(job, time.Now())
							// 刷新后的执行时间小于下次调度时间，还在本次调度内周期内，则任务放入时间轮
							if nowTime+PRE_READ_MS > job.TriggerNextTime {
								ringSecond := (job.TriggerNextTime / 1000) % 60
								s.pushTimeRing(int(ringSecond), job.Id)
								// 刷新下次执行时间，放入时间轮就可以理解为触发了任务
								s.refreshNextValidTime(job, time.Now())
							}
						} else {
							// 本次调度内的任务
							// 放入时间轮
							ringSecond := (job.TriggerNextTime / 1000) % 60
							s.pushTimeRing(int(ringSecond), job.Id)
							// 刷新下次执行时间
							s.refreshNextValidTime(job, time.UnixMilli(job.TriggerNextTime))
						}
					}

					// 更新所有任务
					for _, job := range jobs {
						dao.ScheduleUpdate(job)
					}
				} else {
					// 本次调度周期无任务
					preReadSuc = false
				}

				c := time.Now().UnixMilli() - startTime
				if c < 1000 {
					var sleepTime int64
					if preReadSuc {
						sleepTime = 1000 - time.Now().UnixMilli()%1000
					} else {
						sleepTime = PRE_READ_MS - time.Now().UnixMilli()%1000
					}
					time.Sleep(time.Duration(sleepTime) * time.Millisecond)
				}
			}

		}
	}()
}
func (s *ScheduleHelper) startRing() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Printf("scheduler err %v\n", err)
			}
		}()

		for {
			select {
			case <-s.stopFlag:
				// TODO 优雅停机
			default:
				// 睡眠
				// 每秒触发一次，触发时间花费不到 1s，剩余的时间睡眠等待过去。
				time.Sleep(time.Duration(1000-time.Now().UnixMilli()%1000) * time.Millisecond)

				var ringItemData []int
				// 当前秒
				nowSecond := time.Now().Second()

				// 循环两次，取出当前秒任务以及上一秒任务。
				// 兜底，以防上一秒任务没有执行
				for i := 0; i < 2; i++ {
					tempData, ok := s.ringData.LoadAndDelete((nowSecond + 60 - i) % 60)
					if ok {
						if v, ok := tempData.([]int); ok {
							ringItemData = append(ringItemData, v...)
						}
					}
				}

				if len(ringItemData) > 0 {
					for i := 0; i < len(ringItemData); i++ {
						// 触发
						// TODO 枚举
						trigger.AddTrigger(ringItemData[i], "CRON", -1, "", "", "")
					}

					// 清空 ringItemData
					ringItemData = nil
				}
			}
		}
	}()
}

// refreshNextValidTime 刷新定时任务下次执行时间
// job 任务信息
// fromTime 从这个时间往后计算
func (s *ScheduleHelper) refreshNextValidTime(job *model.XxlJobInfo, fromTime time.Time) {
	t, err := generateNextValidTime(job.ScheduleType, fromTime, job.ScheduleConf)
	if err != nil {
		job.TriggerStatus = 0
		job.TriggerLastTime = 0
		job.TriggerNextTime = 0

		fmt.Printf("job refreshNextValidTime fail jobId=%d, scheduleType=%s, scheduleConf=%s \n", job.Id, job.ScheduleType, job.ScheduleConf)
	}
	job.TriggerLastTime = job.TriggerNextTime
	job.TriggerNextTime = t.UnixMilli()
}

// pushTimeRing 放入时间轮
// ringScond 时间轮时间刻度
// jobId 任务ID
func (s *ScheduleHelper) pushTimeRing(ringSecond, jobId int) {
	tempData, ok := s.ringData.Load(ringSecond)
	if !ok {
		// no data
		tempData = []int{jobId}
		s.ringData.Store(ringSecond, tempData)
	} else {
		if v, ok := tempData.([]int); ok {
			v = append(v, jobId)
			s.ringData.Store(ringSecond, v)
		}
	}
}
